/**
 * Jul 12, 2012
 */
package ee.tallink.common.controller;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.ws.rs.POST;
import javax.ws.rs.core.Response;

import org.javalite.activejdbc.Model;

import ee.tallink.common.ServiceException;
import ee.tallink.common.resource.Resource;

/**
 * @author andrei
 *
 */
public abstract class ResourceController<R extends Resource, M extends Model> {

	protected Class<M> mClass;
	protected Class<R> rClass;

	@SuppressWarnings("unchecked")
	public ResourceController() {
		// This construction works only for those classes that are
		// directly extended from current class
		Type type = getClass().getGenericSuperclass();
		ParameterizedType paramType = (ParameterizedType) type;
		rClass = (Class<R>) paramType.getActualTypeArguments()[0];
		mClass = (Class<M>) paramType.getActualTypeArguments()[1];
	}
	
	@POST
	public Response createResource(R r) {
		M m = createModelObject();
		transferResourceToModel(r, m);
		if (!m.save())
			throw new ServiceException(Response.Status.INTERNAL_SERVER_ERROR);
		r.id = m.getInteger("id");
//		transferModelToResource(m, r);
		return Response.status(201).entity(r).build();
	}

	protected M createModelObject() {
		try {
			return mClass.newInstance();
		} catch (Exception e) {
			throw new ServiceException(e);
		}
	}

	/**
	 * @param m
	 * @param r
	 */
	protected static void transferResourceToModel(Resource r, Model m) {
		try {
			for (Field f : r.getClass().getFields()) {
				ee.tallink.common.db.Field a = f
						.getAnnotation(ee.tallink.common.db.Field.class);
				if (a != null && f.get(r) != null) {
					String fieldName = getModelFieldName(f.getName());
					if (a.name() != null && !a.name().isEmpty())
						fieldName = a.name();

					if (Resource.class.isAssignableFrom(f.getType())) {
						Resource subObject = (Resource) f.get(r);
						if (subObject.id != null)
							m.set(fieldName, subObject.id);
					} else {
						if (f.getType().equals(java.util.Date.class)) {
							m.setTimestamp(fieldName, f.get(r));
						} else
							m.set(fieldName, f.get(r));
					}

				}
			}
		} catch (Exception e) {
			throw new ServiceException(e);
		}
		
	}

	/**
	 * @param r
	 * @param m
	 */
	protected static void transferModelToResource(Model m, Resource r) {
		try {
			for (Field f : r.getClass().getFields()) {
				ee.tallink.common.db.Field a = f
						.getAnnotation(ee.tallink.common.db.Field.class);
				if (a != null) {
					String fieldName = getModelFieldName(f.getName());
					if (a.name() != null && !a.name().isEmpty())
						fieldName = a.name();

					if (Resource.class.isAssignableFrom(f.getType())) {
						Resource subObject = (Resource) f.getType()
								.newInstance();
						// subObject.code = m.getString(fieldName);
						subObject.id = m.getInteger(fieldName);
						f.set(r, subObject);
					} else {
						if (f.getType().equals(Double.class))
							f.set(r, m.getDouble(fieldName));
						else if (f.getType().equals(Long.class))
							f.set(r, m.getLong(fieldName));
						else if (f.getType().equals(Integer.class))
							f.set(r, m.getInteger(fieldName));
						else
							f.set(r, m.get(fieldName));
					}
				}
			}
		} catch (Exception e) {
			throw new ServiceException(e);
		}
	}

	public static String getModelFieldName(String resourceFieldName) {
		Pattern p = Pattern.compile("(\\p{Upper})");
		Matcher m = p.matcher(resourceFieldName);
		StringBuffer sb = new StringBuffer();
		while (m.find()) {
			m.appendReplacement(sb, "_" + m.group(0).toLowerCase());
		}
		m.appendTail(sb);
		return sb.toString();
	}

}
